/*
 * Copyright 2013 Rivaldo.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jety.server;

import com.google.gson.JsonObject;
import java.io.IOException;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import org.jety.commons.domain.HandlerManager;
import org.jety.commons.domain.JsonRpcMethod;
import org.jety.commons.util.AsynchronousUtil;
import org.jety.commons.util.CommunicationUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Class to be instantiated by the servlet.
 * @author Rivaldo
 */
public class ServletExecutor {

    private final HandlerManager handlerManager;
    private static final Logger LOG = LoggerFactory.getLogger(ServletExecutor.class);

    /**
     * Create a new server.
     */
    public ServletExecutor() {
        this.handlerManager = new HandlerManager();
    }

    /**
     * Add a new handler class to treat the client request.
     * @param <T> the handler class type
     * @param name a name to identfy the handler
     * @param handler an instance of the handle
     * @param classes the interfaces implemented by the handler
     * @throws RuntimeException erro while creating the handler
     */
    public <T> void addHandler(String name, T handler, Class<T>... classes) throws RuntimeException {
        handlerManager.addHandler(name, handler, classes);
    }

    /**
     * Executes a request from the client.
     * @param transport the transport object containing the request
     */
    public void execute(JsonRpcServerTransport transport) {

        handlerManager.lockHandlers();

        try {
            JsonObject req = transport.readRequest();
            JsonRpcMethod method = CommunicationUtil.getRequestMethod(req);
            Method handlerMethod = handlerManager.getHandlerMethod(method);

            if (AsynchronousUtil.isAsynchronous(handlerMethod)) {
                executeAsync(method, handlerMethod, transport.getRequest());
            } else {
                executeSync(method, handlerMethod, transport);
            }

        } catch (Exception ex) {
            LOG.error("Error while executing a request.", ex);
        }
    }

    /**
     * Executes the requested method asynchronously.
     * @param method the requested method coded in json
     * @param handlerMethod the java method handler
     * @param request the http request sent by the client
     */
    private void executeAsync(JsonRpcMethod method, Method handlerMethod, HttpServletRequest request) {

        try {
            AsynchronousTransport asyncTransport = new AsynchronousTransport(request);
            Object[] parameters = method.getAsynchronousParams(handlerMethod, asyncTransport);

            Object result = handlerMethod.invoke(handlerManager.getHandler(method), parameters);
            JsonObject response = CommunicationUtil.createResponse(method.getRequestID(), result);

            sendAsyncResponse(response, asyncTransport);
        } catch (Exception ex) {
            LOG.error("Error while executing an asynchronous request.", ex);
        }
    }

    /**
     * Executes the requested method synchronously.
     * @param method the requested method coded in json
     * @param handlerMethod the java method handler
     * @param transport the transport object containing the request
     */
    private void executeSync(JsonRpcMethod method, Method handlerMethod, JsonRpcServerTransport transport) {
        try {
            Object[] parameters = method.getTypedParams(handlerMethod);
            Object result = handlerMethod.invoke(handlerManager.getHandler(method), parameters);
            JsonObject response = CommunicationUtil.createResponse(method.getRequestID(), result);

            sendSyncResponse(response, transport);
        } catch (Exception ex) {
            LOG.error("Error while executing a synchronous request.", ex);
        }
    }

    /**
     * Send a synchronous response to the client.
     * @param response the response coded in json
     * @param transport the transport responsible for send the data
     * @throws IOException erro while sending the data
     */
    private void sendSyncResponse(JsonObject response, JsonRpcServerTransport transport) throws IOException {
        String responseData = response.toString();
        transport.writeResponse(responseData);
    }

    /**
     * Send an asynchronous response to the client.
     * @param response the response coded in json
     * @param asyncTransport the transport responsible for send the data
     * @throws Exception erro while sending the data
     */
    private void sendAsyncResponse(JsonObject response, AsynchronousTransport asyncTransport) throws Exception {
        String responseData = response.toString();
        asyncTransport.writeResponse(responseData);
    }
}
